package com.ztesoft.book.common.utils.aliyunoss;

import cn.hutool.core.util.StrUtil;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.ztesoft.book.common.utils.exception.ServiceException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @Description OSS简易工具类
 * @Author YangFan
 * @Date 2021-07-15 16:29
 */
@Component
@Slf4j
public class AliyunOssUtils {

    private static final Map<String, String> FILE_TYPE_MAP = new HashMap<>(10);

    static {
        // images
        FILE_TYPE_MAP.put("FFD8FFE", "[jpg]");
        FILE_TYPE_MAP.put("89504E47", "[png]");
        FILE_TYPE_MAP.put("47494638", "[gif]");
        FILE_TYPE_MAP.put("49492A00", "[tif]");
        FILE_TYPE_MAP.put("424D", "[bmp]");
        // CAD
        FILE_TYPE_MAP.put("41433130", "[dwg]");
        // PhotoShop
        FILE_TYPE_MAP.put("38425053", "[psd]");
        // 富文本
        FILE_TYPE_MAP.put("7B5C727466", "[rtf]");
        // OFFICE（doc 与 xls 一致，docx 与 xlsx一致）
        FILE_TYPE_MAP.put("D0CF11E0", "[doc][xls][xlsm]");
        FILE_TYPE_MAP.put("504B0304", "[docx][xlsx][xlsm]");
        // 网页
        FILE_TYPE_MAP.put("3C21444F435459504520", "[html]");
        FILE_TYPE_MAP.put("3C68746D6C", "[html]");
        // 音视频（rm 与 r mvb一致，asf 与 wmv 一致）
        FILE_TYPE_MAP.put("57415645", "wav");
        FILE_TYPE_MAP.put("41564920", "avi");
        FILE_TYPE_MAP.put("2E524D46", "[rm][rmvb]");
        FILE_TYPE_MAP.put("000001BA", "mpg");
        FILE_TYPE_MAP.put("000001B3", "mpg");
        FILE_TYPE_MAP.put("6D6F6F76", "mov");
        FILE_TYPE_MAP.put("3026B2758E66CF11", "[asf][wmv]");
        FILE_TYPE_MAP.put("4D546864", "[mid]");
        // 压缩文件
        FILE_TYPE_MAP.put("52617221", "[rar]");
        FILE_TYPE_MAP.put("1F8B08", "[gz]");
        // 其它
        FILE_TYPE_MAP.put("255044462D312E", "[pdf]");
        FILE_TYPE_MAP.put("25504446", "[pdf]");
    }

    /**
     * 阿里云的配置参数
     */
    private static String accessKeyId;
    private static String accessKeySecret;
    private static String bucketName;
    private static String endpoint;

    private static final String POINT = ".";

    @Value("${book.oss.accessKeyId}")
    public void setAccessKeyId(String accessKeyId) {
        setAccessKeyIdValue(accessKeyId);
    }

    public static void setAccessKeyIdValue(String accessKeyId) {
        AliyunOssUtils.accessKeyId = accessKeyId;
    }

    @Value("${book.oss.accessKeySecret}")
    public void setAccessKeySecret(String accessKeySecret) {
        setAccessKeySecretValue(accessKeySecret);
    }

    public static void setAccessKeySecretValue(String accessKeySecret) {
        AliyunOssUtils.accessKeySecret = accessKeySecret;
    }

    @Value("${book.oss.bucket}")
    public void setBucketName(String bucketName) {
        setBucketNameValue(bucketName);
    }

    public static void setBucketNameValue(String bucketName) {
        AliyunOssUtils.bucketName = bucketName;
    }

    @Value("${book.oss.endPoint}")
    public void setEndpoint(String endpoint) {
        setEndpointValue(endpoint);
    }

    public static void setEndpointValue(String endpoint) {
        AliyunOssUtils.endpoint = endpoint;
    }

    /**
     * oss客户端
     *
     * @return
     */
    public static OSS getClient() {
        return new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
    }

    /**
     * 获取一个随机的文件名
     *
     * @param oranName 初始的文件名
     * @return 返回加uuid后的文件名
     */
    private static String getRandomImageName(String oranName, String filename) {
        //新的文件名
        SecureRandom random = new SecureRandom();
        String randomS = String.valueOf(random.nextInt(90000) + 10000);
        //新的文件名
        String newFilename = System.currentTimeMillis() + randomS + "." + StringUtils.getFilenameExtension(filename);
        //文件存放的文件夹 example: 20170401/XXX.file
        String fileDirectory = "/" + DateFormatUtils.format(new Date(), "yyyyMMdd") + "/";
        String uploadFilePath = oranName + fileDirectory + newFilename;
        return uploadFilePath;
    }

    /**
     * 上传文件流
     *
     * @param oranFileName 上传到服务器上的文件路径和名称
     * @param file         来自本地的文件或者文件流
     */
    public static String uploadFileInputSteam(String oranFileName, MultipartFile file) {
        // <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg
        String objectName = getRandomImageName(oranFileName, file.getOriginalFilename());
        if (StrUtil.isEmpty(objectName)) {
            throw new ServiceException("文件错误，请重新上传文件");
        }
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        // 上传文件流
        try (InputStream inputStream = file.getInputStream()) {
            int i = inputStream.available();
            if (i == 0) {
                throw new ServiceException("请勿上传空文件");
            }
            //验证文件后缀名
            String filenameExtension = StringUtils.getFilenameExtension(objectName).toLowerCase();
            if (checkIllegalFile(inputStream, filenameExtension)) {
                throw new ServiceException("文件类型错误，请重新上传文件");
            }
            //上传到OSS
            ossClient.putObject(bucketName, objectName, file.getInputStream());
        }
        catch (FileNotFoundException e) {
            log.error("uploadFileInputSteam", e);
        }
        catch (IOException ex) {
            log.error("uploadFileInputSteam-IO", ex);
        }
        // 关闭OSSClient。
        ossClient.shutdown();
        //返回文件在服务器上的全路径+名称
        return "/" + objectName;
    }

    /**
     * 验证文件是否合法.
     *
     * @param inputStream       文件流
     * @param filenameExtension 文件后缀名
     * @return boolean
     */
    public static boolean checkIllegalFile(final InputStream inputStream, final String filenameExtension) {
        //给后缀名、文件类型加上括号，便于头信息相同而文件类型不同情况下的比较
        String filenameExtension2 = "[" + filenameExtension + "]";
        //根据头信息获取真实文件类型
        String trueFileType = getFileType(inputStream);
        //遍历验证名单，进行文件类型校验
        Iterator<Map.Entry<String, String>> entries = FILE_TYPE_MAP.entrySet().iterator();
        //特殊情况：【图片类型】直接通过修改后缀名来切换图片类型，其头信息不变
        List<String> imgTypeList = Arrays.asList("[jpg]", "[png]", "[gif]", "[tif]", "[bmp]");
        while (entries.hasNext()) {
            Map.Entry<String, String> entry = entries.next();
            String mapKey = entry.getKey();
            String mapValue = FILE_TYPE_MAP.get(mapKey);
            //当文件后缀名在验证名单中，则验证（true：非法  false：合法）
            if (mapValue.indexOf(filenameExtension2) == -1) {
                continue;
            }
            //若根据头信息字典查询不到文件类型，则非法
            if ("".equals(trueFileType)) {
                return true;
            }
            if (imgTypeList.contains(filenameExtension2) && imgTypeList.contains(trueFileType)) {
                return false;
            }
            return trueFileType.indexOf(filenameExtension2) == -1;
        }
        return false;
    }


    /**
     * 获取文件头
     *
     * @param is 文件流
     * @return String
     */
    private static String getFileHeader(InputStream is) {
        String value = null;
        log.info("获取文件头");
        try {
            byte[] b = new byte[4];
            int size = is.read(b, 0, b.length);
            if (size == 0) {
                log.error("获取头文件为空");
            }
            value = bytesToHexString(b);
        }
        catch (Exception e) {
            log.error(e.getMessage());
        }
        return value;
    }

    /**
     * 根据头信息获取文件类型.
     *
     * @param is 文件流
     * @return String
     */
    public static String getFileType(final InputStream is) {
        String head = getFileHeader(is);
        Iterator<Map.Entry<String, String>> entries = FILE_TYPE_MAP.entrySet().iterator();
        while (entries.hasNext()) {
            Map.Entry<String, String> entry = entries.next();
            if (head.startsWith(entry.getKey())) {
                return entry.getValue();
            }
        }
        return "";
    }

    private static String bytesToHexString(byte[] src) {
        StringBuilder builder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        String hv;
        for (Byte b : src) {
            // 以十六进制（基数 16）无符号整数形式返回一个整数参数的字符串表示形式，并转换为大写
            hv = Integer.toHexString(b & 0xFF)
                    .toUpperCase();
            if (hv.length() < 2) {
                builder.append(0);
            }
            builder.append(hv);
        }
        return builder.toString();
    }

    /**
     * 上传文件流
     *
     * @param oranFileName 上传到服务器上的文件路径和名称
     * @param file         来自本地的文件或者文件流
     */
    public static String uploadFileInputSteam(String oranFileName, File file) {
        // <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径，例如abc/efg/123.jpg
        String objectName = getRandomImageName(oranFileName, file.getName());
        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        // 上传文件流。
        try (InputStream inputStream = new FileInputStream(file);) {
            //上传到OSS
            ossClient.putObject(bucketName, objectName, inputStream);
        }
        catch (Exception e) {
            log.error("uploadFileInputSteam", e);
        }
        // 关闭OSSClient。
        ossClient.shutdown();
        //返回文件在服务器上的全路径+名称
        return "/" + objectName;
    }
}